use libp2p::{PeerId, gossipsub, identify, kad, mdns, request_response};
use tokio::sync::oneshot;
use tracing::{debug, info};

use crate::network_api::P2PEventForApp;
use crate::network_core::P2pNetWorkError;
use crate::network_core::network_core_impl::combined_behaviour::{
    CombinedBehaviour, ContentRequest, ContentResponse,
};
use crate::network_core::network_core_impl::combined_behaviour::{
    KadEventMetaData, RequestResponseEventMetaData,
};

// handle events
impl CombinedBehaviour {
    pub fn handle_kademlia_event(
        &mut self,
        kad_metadata: &mut KadEventMetaData,
        event: kad::Event,
    ) {
        match event {
            kad::Event::OutboundQueryProgressed {
                id,
                result: kad::QueryResult::StartProviding(_),
                ..
            } => {
                let sender: oneshot::Sender<()> = kad_metadata
                    .pending_start_providing
                    .remove(&id)
                    .expect("Completed query to be previously pending.");
                let _ = sender.send(());
            }

            kad::Event::OutboundQueryProgressed {
                id,
                result:
                    kad::QueryResult::GetProviders(Ok(kad::GetProvidersOk::FoundProviders {
                        providers,
                        ..
                    })),
                ..
            } => {
                debug!("***---+++Found providers: {:?}", providers);
                if let Some(sender) = kad_metadata.pending_get_providers.remove(&id) {
                    sender.send(providers).expect("Receiver not to be dropped");

                    // Finish the query. We are only interested in the first result.
                    self.kademlia.query_mut(&id).unwrap().finish();
                }
            }

            kad::Event::OutboundQueryProgressed {
                result:
                    kad::QueryResult::GetProviders(Ok(
                        kad::GetProvidersOk::FinishedWithNoAdditionalRecord { .. },
                    )),
                ..
            } => {
                debug!("***---+++Finished with no additional record");
            }

            e => {
                debug!("Kademlia event: {:?}", e);
            }
        }
    }

    // network to local event
    pub async fn handle_request_response_event(
        &mut self,
        request_response_metadata: &mut RequestResponseEventMetaData,
        event: request_response::Event<ContentRequest, ContentResponse>,
    ) {
        match event {
            request_response::Event::Message { message, .. } => match message {
                request_response::Message::Request {
                    request, channel, ..
                } => {
                    // receive request from external
                    request_response_metadata
                        .event_sender_for_app
                        .send(P2PEventForApp::InboundRequest {
                            requested_content: request.0,
                            channel,
                        })
                        .await
                        .expect("Event receiver not to be dropped.");
                }
                request_response::Message::Response {
                    request_id,
                    response,
                } => {
                    // receive response from external
                    let _ = request_response_metadata
                        .pending_request_file
                        .remove(&request_id)
                        .expect("Request to still be pending.")
                        .send(Ok(response.0.unwrap()));
                }
            },

            request_response::Event::OutboundFailure {
                request_id, error, ..
            } => {
                let _ = request_response_metadata
                    .pending_request_file
                    .remove(&request_id)
                    .expect("Request to still be pending.")
                    .send(Err(P2pNetWorkError::OutboundFailure(error)));
            }

            request_response::Event::ResponseSent { .. } => {
                // todo!()
                info!("ResponseSent")
            }
            request_response::Event::InboundFailure { .. } => {
                todo!()
            }
        }
    }

    pub fn handle_mdns_event(&mut self, event: mdns::Event, local_peer_id: &PeerId) {
        match event {
            mdns::Event::Discovered(peers) => {
                for (peer_id, multiaddr) in peers {
                    // println!("mDNS discovered a new peer: {peer_id}");
                    println!("[{local_peer_id}] Found peer {peer_id} at {}", multiaddr); // MARK, depended by test_p2p_discovery.py
                    self.gossipsub.add_explicit_peer(&peer_id);
                    // self.sw
                }
            }
            mdns::Event::Expired(peers) => {
                for (peer_id, _multiaddr) in peers {
                    println!("mDNS discover peer has expired: {peer_id}");
                    self.gossipsub.remove_explicit_peer(&peer_id);
                }
            }
        }
    }

    pub async fn handle_gossipsub_event(
        &mut self,
        event: gossipsub::Event,
        local_peer_id: &PeerId,
        event_sender_for_app: &tokio::sync::mpsc::Sender<P2PEventForApp>,
    ) {
        match event {
            gossipsub::Event::Message {
                propagation_source: peer_id,
                message_id: id,
                message,
            } => {
                match String::from_utf8(message.data.clone()) {
                    Ok(data_str) => {
                        println!(
                            "[{local_peer_id}] Got message: '{}' with id: {id} from peer: {peer_id}", // MARK
                            data_str,
                        )
                    }
                    Err(_) => {
                        println!(
                            "[{local_peer_id}] Got message: '{:?}' with id: {id} from peer: {peer_id}, topic: {}",
                            message.data, message.topic
                        );
                    }
                };

                // Send the received broadcast message to the APP layer.
                event_sender_for_app
                    .send(P2PEventForApp::MessageReceived {
                        topic: message.topic.to_string(),
                        from_peer: peer_id,
                        data: message.data,
                    })
                    .await
                    .expect("Event receiver not to be dropped.");
            }

            gossipsub::Event::Subscribed { peer_id, topic } => {
                println!("Peer({peer_id}) has subscribed to topic {topic:?}")
            }

            gossipsub::Event::Unsubscribed { peer_id, topic } => {
                println!("Peer({peer_id}) has unsubscribed from topic {topic:?}")
            }

            gossipsub::Event::GossipsubNotSupported { peer_id } => {
                println!("Peer({peer_id}) does not support Gossipsub!")
            }

            gossipsub::Event::SlowPeer { .. } => {} // _ => {}
        }
    }

    pub fn handle_identify_event(&mut self, event: identify::Event) {
        match event {
            // Prints peer id identify info is being sent to.
            identify::Event::Sent {
                connection_id,
                peer_id,
            } => {
                info!("[Identify] [{connection_id:?}] Sent identify info to {peer_id:?}")
            }

            // Prints out the info received via the identify event
            identify::Event::Received {
                connection_id,
                peer_id,
                info,
            } => {
                info!("[Identify] [{connection_id:?}] [{peer_id:?}] Received {info:?}")
            }

            identify::Event::Error {
                connection_id,
                peer_id,
                error,
            } => {
                println!(
                    "[Identify] Identify error from {peer_id:?} on connection {connection_id:?}: {error:?}"
                )
            }
            identify::Event::Pushed {
                connection_id,
                peer_id,
                info,
            } => {
                println!(
                    "[Identify] Pushed identify info to {peer_id:?} on connection {connection_id:?}: {info:?}"
                )
            }
        }
    }
}
